﻿using System;
using System.ServiceModel;
using AirlineServer.AlliancesRegistartionServiceReference;
using log4net;
using Util;

namespace AirlineServer
{
    internal partial class ClusterManager : Cluster.ClusterManager
    {
        [Serializable]
        private class AirlineClusterNode : ClusterNode
        {
            public string Uri; // the intra-cluster communcation service uri
        }

        private readonly ILog log = LogManager.GetLogger(typeof(ClusterManager));

        private readonly FlightsDatabase fdb;

        private readonly string clusterServiceUri;

        private readonly string localServiceUri;

        private readonly string remoteSearchServiceUri;

        private ServiceHost searchServiceHost;

        private readonly ConnectionsCache<IClusterService> clusterConnectionsCache = new ConnectionsCache<IClusterService>();

        private int currentNodesInCluster;

        private bool isDelegate;

        public QueryCache Cache { get; set; }

        public ClusterManager(FlightsDatabase fdb, string clusterName, string serverName,
            string clusterServiceUri, string localServiceUri, string remoteSearchServiceUri)
            : base(clusterName, serverName)
        {
            this.fdb = fdb;
            this.clusterServiceUri = clusterServiceUri;
            this.localServiceUri = localServiceUri;
            this.remoteSearchServiceUri = remoteSearchServiceUri;

            ClusterViewChanged += ((o, e) => OnClusterViewChange());
        }

        public override void Stop()
        {
            if (searchServiceHost != null)
            {
                searchServiceHost.Abort();
            }

            base.Stop();
        }

        protected override ClusterNode CreateClsuterNode()
        {
            return new AirlineClusterNode
            {
                Uri = clusterServiceUri,
            };
        }

        protected override void TakeLeadership()
        {
            if (log.IsDebugEnabled)
            {
                log.Info("Elected to leader");
            }

            isDelegate = true;

            try
            {
                Cache = new QueryCache();

                var ss = new SearchService
                {
                    ClusterManager = this,
                };

                if (log.IsInfoEnabled)
                {
                    log.Info("Starting search service on " + localServiceUri);
                }

                searchServiceHost = new ServiceHost(ss, new Uri(localServiceUri));
                searchServiceHost.Open();

                if (log.IsInfoEnabled)
                {
                    log.Info("... OK");
                }
            }
            catch (Exception e)
            {
                if (log.IsFatalEnabled)
                {
                    log.Fatal("... Failed", e);
                }

                return;
            }

            try
            {
                if (log.IsInfoEnabled)
                {
                    log.Info("Registering delegate on search server");
                }

                using (var connection = new SoapConnection<IRegistartionService>(remoteSearchServiceUri))
                {
                    connection.Channel.RegisterAlliance(ClusterName, localServiceUri);
                }

                if (log.IsInfoEnabled)
                {
                    log.Info("... OK");
                }
            }
            catch (Exception e)
            {
                if (log.IsErrorEnabled)
                {
                    log.Error("Failed to register delegate on search server", e);
                }
            }
        }

        private void OnClusterViewChange()
        {
            if (isDelegate && ClusterView.Count > currentNodesInCluster)
            {
                // a new server joined - clear the cache
                Cache.Clear();
            }

            currentNodesInCluster = ClusterView.Count;
        }

        protected override void RequestReplicationFromRemoteNode(string name, ClusterNode node)
        {
            var flights = fdb.GetServerFlights(name);
            var connection = clusterConnectionsCache.GetConnection(((AirlineClusterNode) node).Uri);
            connection.Channel.Replicate(name, flights);
        }

        protected override void RequestDiscardReplicationFromRemoteNode(string name, ClusterNode node)
        {
            var connection = clusterConnectionsCache.GetConnection(((AirlineClusterNode) node).Uri);
            connection.Channel.DiscardReplication(name);
        }        
    }
}
